﻿using S7Cat.Enums;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;

namespace S7Cat
{
    public sealed class ConnectionPool : IDisposable
    {
        private readonly ConcurrentQueue<PooledPlc> _pool = [];
        private readonly SemaphoreSlim _semaphore;

        public CpuType Cpu { get; init; }
        public string Ip { get; init; }
        public int Port { get; init; }
        public short Rack { get; init; }
        public short Slot { get; init; }

        public int MaxCount { get; init; }

        public bool IsConnected { get; private set; }

        public ConnectionPool(CpuType cpu, string ip, short rack, short slot, int maxCount) : this(cpu, ip, Plc.DEFAULT_PORT, rack, slot, maxCount) { }
        public ConnectionPool(CpuType cpu, string ip, int port, short rack, short slot, int maxCount)
        {
            Cpu = cpu;
            Ip = ip;
            Port = port;
            Rack = rack;
            Slot = slot;

            MaxCount = maxCount;
            _semaphore = new SemaphoreSlim(MaxCount, MaxCount);
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private PooledPlc CreatePooledPlc()
        {
            return new(Cpu, Ip, Port, Rack, Slot, this);
        }

        public async Task InitalizeAsync(CancellationToken token = default)
        {
            var list = new List<PooledPlc>(MaxCount);
            try
            {
                for (int i = 0; i < MaxCount; i++)
                {
                    list.Add(await GetAvailableAsync(token).ConfigureAwait(false));
                }

                IsConnected = true;
            }
            catch (Exception)
            {
                IsConnected = false;
            }
            finally
            {
                foreach (var plc in list)
                {
                    plc.Dispose();
                }
            }
        }

        public async Task<PooledPlc> GetAvailableAsync(CancellationToken token = default)
        {
            await _semaphore.WaitAsync(token).ConfigureAwait(false);

            if (!_pool.TryDequeue(out var plc))
            {
                plc = CreatePooledPlc();
            }
            try
            {
                if (!plc.IsConnected)
                {
                    await plc.OpenAsync(token).ConfigureAwait(false);
                }
                plc.IsReturned = false;

                return plc;
            }
            catch (Exception)
            {
                _semaphore.Release();
                plc.Close();
                throw;
            }
        }

        public void Return(PooledPlc item)
        {
            if (isDisposed)
            {
                item.Close();
                return;
            }

            _pool.Enqueue(item);
            _semaphore.Release();
        }

        private bool isDisposed = false;
        public void Dispose()
        {
            if (!isDisposed)
            {
                isDisposed = true;
                _semaphore.Dispose();

                foreach (var plc in _pool)
                {
                    plc.Close();
                }

                GC.SuppressFinalize(this);
            }
        }

        ~ConnectionPool()
        {
            Dispose();
        }
    }
}
